﻿#region Copyright information
// 
// Copyright © 2005-2013 Yongkee Cho. All rights reserved.
// 
// This code is a part of the Biological Object Library and governed under the terms of the
// GNU Lesser General  Public License (LGPL) version 2.1 which accompanies this distribution.
// For more information on the LGPL, please visit http://bol.codeplex.com/license.
// 
// - Filename: Locus.cs
// - Author: Yongkee Cho
// - Email: yongkeecho@gmail.com
// - Date Created: 2013-01-25 3:25 PM
// - Last Modified: 2013-01-25 3:59 PM
// 
#endregion
using System;
using System.Runtime.Serialization;
using System.Text;
using System.Text.RegularExpressions;

namespace BOL.Models.Genome
{
    /// <summary>
    /// Represent a specific location of a gene or a DNA sequence on a chromosome.
    /// It is recommended to use Range of Locus when needed to represent a range of location such as 11q1.4-q2.1.
    /// <see cref="http://en.wikipedia.org/wiki/Locus_(genetics)"/>
    /// </summary>
    [DataContract]
    public class Locus : IComparable, IComparable<Locus>, IEquatable<Locus>
    {
        #region public variables

        [DataMember]
        public ChromosomeArm Arm { get; private set; }

        [DataMember]
        public byte Band { get; private set; }

        [DataMember]
        public byte Section { get; private set; }

        [DataMember]
        public byte SubBand { get; private set; }

        [DataMember]
        public byte SubSubBand { get; private set; }

        #endregion

        #region Constructor

        public Locus(ChromosomeArm arm, byte band, byte section, byte subBand, byte subSubBand)
        {
            Arm = arm;
            Band = band;
            Section = section;
            SubBand = subBand;
            SubSubBand = subSubBand;
        }

        #endregion

        #region Conversion methods

        public static Locus Parse(string str)
        {
            var arm = ChromosomeArm.Unknown;
            byte band, section, subBand, subSubBand;

            var match = Regex.Match(str, @"(?<chrArm>[pq])(?<band>[1-9])(?<section>[1-9]{0,1})(.{0,1})(?<subBand>[1-9]{0,1})(?<subSubBand>[1-9]{0,1})");
            var group = match.Groups;

            char chrArm;
            if (Char.TryParse(group["chrArm"].Value, out chrArm))
            {
                switch (chrArm)
                {
                    case 'P':
                    case 'p':
                        arm = ChromosomeArm.P;
                        break;
                    case 'Q':
                    case 'q':
                        arm = ChromosomeArm.Q;
                        break;
                    default:
                        arm = ChromosomeArm.Unknown;
                        break;
                }
            }

            if (Byte.TryParse(group["band"].Value, out band)) band = 0;
            if (Byte.TryParse(group["section"].Value, out section)) section = 0;
            if (Byte.TryParse(group["subBand"].Value, out subBand)) subBand = 0;
            if (Byte.TryParse(group["subSubBand"].Value, out subSubBand)) subSubBand = 0;

            return new Locus(arm, band, section, subBand, subSubBand);
        }

        public static bool TryParse(string str, out Locus result)
        {
            result = null;

            try
            {
                result = Parse(str);
            }
            catch
            {
                return false;
            }

            return true;
        }

        #endregion

        #region IComparable, IComparable<Locus>, IEquatable<Locus> implementation

        public int CompareTo(object other)
        {
            if (other == null)
                throw new NullReferenceException();

            if (!(other is Locus))
                throw new InvalidCastException("The 'other' argument is not a Locus object.");

            return CompareTo((Locus)other);
        }

        public int CompareTo(Locus other)
        {
            var compare = Arm.CompareTo(other.Arm);

            if (compare == 0)
            {
                if (Arm == ChromosomeArm.P)
                {
                    compare = -Band.CompareTo(other.Band);
                    if (compare == 0)
                    {
                        compare = -Section.CompareTo(other.Section);
                        if (compare == 0)
                        {
                            compare = -SubBand.CompareTo(other.SubBand);
                            if (compare == 0)
                                compare = -SubSubBand.CompareTo(other.SubSubBand);
                        }
                    }
                }
                else
                {
                    compare = Band.CompareTo(other.Band);
                    if (compare == 0)
                    {
                        compare = Section.CompareTo(other.Section);
                        if (compare == 0)
                        {
                            compare = SubBand.CompareTo(other.SubBand);
                            if (compare == 0)
                                compare = SubSubBand.CompareTo(other.SubSubBand);
                        }
                    }
                }
            }

            return compare;
        }

        public bool Equals(Locus other)
        {
            return Arm.Equals(other.Arm) &
                Band.Equals(other.Band) &
                Section.Equals(other.Section) &
                SubBand.Equals(other.SubBand) &
                SubSubBand.Equals(other.SubSubBand);
        }

        #endregion

        #region Object overriden

        public override int GetHashCode()
        {
            return Arm.GetHashCode() ^ Band ^ Section ^ SubBand ^ SubSubBand;
        }

        public override bool Equals(object other)
        {
            if (other == null)
                throw new ArgumentNullException("other");

            if (!(other is Locus))
                throw new InvalidCastException("The 'other' argument is not a Locus object.");

            return Equals((Locus)other);
        }

        public override string ToString()
        {
            var sb = new StringBuilder((Arm == ChromosomeArm.P) ? 'p' : 'q');
            if (Band != 0) sb.Append(Band);
            if (Section != 0) sb.Append(Section);
            if (SubBand != 0) sb.AppendFormat(".{0}", SubBand);
            if (SubSubBand != 0) sb.Append(SubSubBand);

            return sb.ToString();
        }

        #endregion
    }
}
